package com.wodensoft.core.db;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.hibernate.cache.internal.CacheDataDescriptionImpl;
import org.hibernate.cache.spi.EntityRegion;
import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.RegionFactory;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Settings;
import org.hibernate.engine.spi.CacheImplementor;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.spi.PersisterFactory;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.hibernate.service.spi.SessionFactoryServiceRegistryFactory;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;

import com.wodensoft.core.BeanFactory;
import com.wodensoft.util.ReflectionUtils;

/**
 * Hibernate <code>SessionFactory</code>辅助类.
 * 
 */
public class SessionFactoryHelper {

    /**
     * 配置更新锁
     */
    private static Lock lock = new ReentrantLock();

    /**
     * 获取<code>LocalSessionFactoryBean</code>
     * 
     * @return
     */
    private static Object getLocalSessionFactoryBean() {
        return BeanFactory.getInstance().getBean("&sessionFactory");
    }

    /**
     * 获取<code>Configuration</code>单例
     * 
     * @return
     */
    private static Configuration getConfiguration() {
        return (Configuration) ReflectionUtils.getMethodValue(getLocalSessionFactoryBean(), "getConfiguration");
    }

    /**
     * 获取<code>SessionFactoryImpl</code>单例
     * 
     * @return
     */
    private static SessionFactoryImpl getSessionFactory() {
        return (SessionFactoryImpl) ReflectionUtils.getMethodValue(getLocalSessionFactoryBean(), "getObject");
    }

    /**
     * 重新根据配置信息更新<code>SessionFactory</code>.
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static void refresh(Configuration cfg, SessionFactoryImpl sessionFactory) {
        synchronized (cfg) {
            Mapping mapping = cfg.buildMapping();
            Settings settings = sessionFactory.getSettings();
            Map<String, EntityPersister> entityPersisters = sessionFactory.getEntityPersisters();
            Map<String, IdentifierGenerator> identifierGenerators = (Map<String, IdentifierGenerator>) ReflectionUtils
                    .getFieldValue(sessionFactory, "identifierGenerators");
            Map<String, ClassMetadata> classMetadata = new HashMap<String, ClassMetadata>();
            Map<String, ClassMetadata> classMeta = (Map<String, ClassMetadata>) ReflectionUtils.getFieldValue(
                    sessionFactory, "classMetadata");
            classMetadata.putAll(classMeta);
            Properties properties = sessionFactory.getProperties();

            final ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(properties)
                    .buildServiceRegistry();

            SessionFactoryServiceRegistry sessionFactoryServiceRegistry = serviceRegistry.getService(
                    SessionFactoryServiceRegistryFactory.class).buildServiceRegistry(sessionFactory, cfg);
            CacheImplementor cacheAccess = sessionFactoryServiceRegistry.getService(CacheImplementor.class);

            // 生成数据库
            if (settings.isAutoUpdateSchema()) {
                new SchemaUpdate(serviceRegistry, cfg).execute(false, true);
            }

            // 处理mapping
            for (Iterator<PersistentClass> it = cfg.getClassMappings(); it.hasNext();) {
                PersistentClass model = it.next();
                if (identifierGenerators.containsKey(model.getEntityName())) {
                    continue;
                }

                if (!model.isInherited()) {
                    IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator(
                            cfg.getIdentifierGeneratorFactory(), sessionFactory.getDialect(),
                            settings.getDefaultCatalogName(), settings.getDefaultSchemaName(), (RootClass) model);
                    identifierGenerators.put(model.getEntityName(), generator);
                }

                final RegionFactory regionFactory = cacheAccess.getRegionFactory();
                Map entityAccessStrategies = new HashMap();
                final String cacheRegionPrefix = settings.getCacheRegionPrefix() == null ? "" : settings
                        .getCacheRegionPrefix() + ".";
                model.prepareTemporaryTables(mapping, sessionFactory.getDialect());
                final String cacheRegionName = cacheRegionPrefix + model.getRootClass().getCacheRegionName();
                // cache region is defined by the root-class in the hierarchy...
                EntityRegionAccessStrategy accessStrategy = (EntityRegionAccessStrategy) entityAccessStrategies
                        .get(cacheRegionName);
                if (accessStrategy == null && settings.isSecondLevelCacheEnabled()) {
                    final AccessType accessType = AccessType.fromExternalName(model.getCacheConcurrencyStrategy());
                    if (accessType != null) {
                        EntityRegion entityRegion = regionFactory.buildEntityRegion(cacheRegionName, properties,
                                CacheDataDescriptionImpl.decode(model));
                        accessStrategy = entityRegion.buildAccessStrategy(accessType);
                        entityAccessStrategies.put(cacheRegionName, accessStrategy);
                        cacheAccess.addCacheRegion(cacheRegionName, entityRegion);
                    }
                }

                NaturalIdRegionAccessStrategy naturalIdAccessStrategy = null;
                if (model.hasNaturalId() && model.getNaturalIdCacheRegionName() != null) {
                    final String naturalIdCacheRegionName = cacheRegionPrefix + model.getNaturalIdCacheRegionName();
                    naturalIdAccessStrategy = (NaturalIdRegionAccessStrategy) entityAccessStrategies
                            .get(naturalIdCacheRegionName);

                    if (naturalIdAccessStrategy == null && settings.isSecondLevelCacheEnabled()) {
                        final CacheDataDescriptionImpl cacheDataDescription = CacheDataDescriptionImpl.decode(model);

                        NaturalIdRegion naturalIdRegion = null;
                        try {
                            naturalIdRegion = regionFactory.buildNaturalIdRegion(naturalIdCacheRegionName, properties,
                                    cacheDataDescription);
                        } catch (UnsupportedOperationException e) {
                        }

                        if (naturalIdRegion != null) {
                            naturalIdAccessStrategy = naturalIdRegion.buildAccessStrategy(regionFactory
                                    .getDefaultAccessType());
                            entityAccessStrategies.put(naturalIdCacheRegionName, naturalIdAccessStrategy);
                            cacheAccess.addCacheRegion(naturalIdCacheRegionName, naturalIdRegion);
                        }
                    }
                }

                EntityPersister cp = serviceRegistry.getService(PersisterFactory.class).createEntityPersister(model,
                        accessStrategy, naturalIdAccessStrategy, sessionFactory, mapping);
                entityPersisters.put(model.getEntityName(), cp);
                classMetadata.put(model.getEntityName(), cp.getClassMetadata());
            }
            ReflectionUtils.setFieldValue(sessionFactory, "classMetadata", Collections.unmodifiableMap(classMetadata));
        }
    }

    /**
     * 刷新<code>SessionFactory</code>
     * 
     * @param confg
     */
    public static void refresh(Config confg) {
        try {
            lock.tryLock();
            Configuration cfg = getConfiguration();
            SessionFactoryImpl sessionFactory = getSessionFactory();
            if (confg != null) {
                for (InputStream is : confg.list) {
                    cfg.addInputStream(is);
                }
                refresh(cfg, sessionFactory);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 每次表结构操作的配置VO
     * 
     */
    public static class Config {
        private List<InputStream> list = new ArrayList<InputStream>();

        public void addFile(File file) {
            if (file.exists()) {
                try {
                    list.add(new FileInputStream(file));
                } catch (FileNotFoundException e) {
                    // empty.
                }
            }
        }

        public void addInputStream(InputStream is) {
            list.add(is);
        }
    }

}
